Public health benefits of clean household energy in India¶
An interactive plot visualising how a complete transition to clean household energy in India can save one-quarter of the healthy life lost to total particulate matter pollution exposure in India.
For more information, see the paper here.
BASELINE = Present-day emissions.
ALLLPG = Complete household transition from solid fuels to liquefied petroleum gas (LPG).
PM₂.₅ = Fine particulate matter, annual-mean.
MORT = Annual number of premature mortalities.
# --- modules ---
import pandas as pd
import geopandas as gpd
from bokeh.layouts import column, row
from bokeh.models import (ColumnDataSource, CustomJS, LinearColorMapper,
GeoJSONDataSource, Panel, Tabs,
BasicTicker, ColorBar, Select)
from bokeh.plotting import figure
from bokeh.themes import Theme
from bokeh.io import show, output_notebook, reset_output
from bokeh.palettes import Viridis10, BrBG10
output_notebook()
# --- data ---
path = 'data/'
scenarios = ['BASELINE', 'ALLLPG']#, 'URB15', 'EMIS50', 'STATE50']
gdf = gpd.read_file(path + 'gadm36_IND_1.shp')
geosources = {}
for scenario in scenarios:
df = pd.read_csv(path + 'Conibear_et_al_2020_supp-data_' + scenario + '.csv')[0:34]
df.rename(columns={'Unnamed: 0': 'geo-id'}, inplace=True)
df['scenario'] = scenario
gdf_merged = gdf.merge(df, left_on='NAME_1', right_on='location')
geosource = GeoJSONDataSource(geojson=gdf_merged.to_json())
geosources.update({scenario: geosource})
# --- formating ---
df_formats = {}
plot_labels = [
"Ambient PM\u2082.\u2085 concentrations",
"Household PM\u2082.\u2085 concentrations, females",
"Household PM\u2082.\u2085 concentrations, males",
"Household PM\u2082.\u2085 concentrations, children",
"MORT from total PM\u2082.\u2085 exposure",
"MORT from ambient PM\u2082.\u2085 exposure",
"MORT from household PM\u2082.\u2085 exposure",
"DALYs rate from total PM\u2082.\u2085 exposure",
"DALYs rate from ambient PM\u2082.\u2085 exposure",
"DALYs rate from household PM\u2082.\u2085 exposure"
]
plot_variables = [
'apm25_popweighted',
'hpm25_female_popweighted',
'hpm25_male_popweighted',
'hpm25_child_popweighted',
'mort_tpm25_6cod_mean_total',
'mort_apm25_6cod_mean_total',
'mort_hpm25_6cod_mean_total',
'dalys_tpm25_rate_6cod_mean_total',
'dalys_apm25_rate_6cod_mean_total',
'dalys_hpm25_rate_6cod_mean_total'
]
min_values_abs = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
max_values_abs = [100, 300, 300, 300, 150000, 150000, 150000, 3500, 3500, 3500]
min_values_diff = [-50, -300, -300, -300, -50000, -50000, -50000, -1500, -1500, -1500]
max_values_diff = [50, 300, 300, 300, 50000, 50000, 50000, 1500, 1500, 1500]
df_baseline = pd.read_csv(path + 'Conibear_et_al_2020_supp-data_BASELINE.csv')
for scenario in scenarios:
df = pd.read_csv(path + 'Conibear_et_al_2020_supp-data_' + scenario + '.csv')
if scenario == 'BASELINE':
plot_title = [
('Ambient ' + u'PM\u2082.\u2085' + ' concentration', '(India = ' + str(df.apm25_popweighted.values[-1]) + ' ' + u'\u03bcg' + '/' + u'm\u00b3' + ")"),
('Household ' + u'PM\u2082.\u2085' + ' concentration, females', '(India = ' + str(df.hpm25_female_popweighted.values[-1]) + ' ' + u'\u03bcg' + '/' + u'm\u00b3' + ')'),
('Household ' + u'PM\u2082.\u2085' + ' concentration, males', '(India = ' + str(df.hpm25_male_popweighted.values[-1]) + ' ' + u'\u03bcg' + '/' + u'm\u00b3' + ')'),
('Household ' + u'PM\u2082.\u2085' + ' concentration, children', '(India = ' + str(df.hpm25_child_popweighted.values[-1]) + ' ' + u'\u03bcg' + '/' + u'm\u00b3' + ')'),
('MORT from total ' + u'PM\u2082.\u2085' + ' exposure', '(India = ' + '{:,}'.format(int(round(df.mort_tpm25_6cod_mean_total.values[-1], -3))) + ' deaths)'),
('MORT from ambient ' + u'PM\u2082.\u2085' + ' exposure', '(India = ' + '{:,}'.format(int(round(df.mort_apm25_6cod_mean_total.values[-1], -3))) + ' deaths)'),
('MORT from household ' + u'PM\u2082.\u2085' + ' exposure', '(India = ' + '{:,}'.format(int(round(df.mort_hpm25_6cod_mean_total.values[-1], -3))) + ' deaths)'),
('DALYs rate from total ' + u'PM\u2082.\u2085' + ' exposure', '(India = ' + '{:,}'.format(int(round(df.dalys_tpm25_rate_6cod_mean_total.values[-1]))) + ' DALYs per 100,000 people)'),
('DALYs rate from ambient ' + u'PM\u2082.\u2085' + ' exposure', '(India = ' + '{:,}'.format(int(round(df.dalys_apm25_rate_6cod_mean_total.values[-1]))) + ' DALYs per 100,000 people)'),
('DALYs rate from household ' + u'PM\u2082.\u2085' + ' exposure', '(India = ' + '{:,}'.format(int(round(df.dalys_hpm25_rate_6cod_mean_total.values[-1]))) + ' DALYs per 100,000 people)')
]
else:
plot_title = [
('Ambient ' + u'PM\u2082.\u2085' + ' concentration', '(India = ' + str(df.apm25_popweighted.values[-1]) + ' ' + u'\u03bcg' + '/' + u'm\u00b3' + ", " + str(int(100 * df.apm25_popweighted.values[-1] / df_baseline.apm25_popweighted.values[-1])) + "%)"),
('Household ' + u'PM\u2082.\u2085' + ' concentration, females', '(India = ' + str(df.hpm25_female_popweighted.values[-1]) + ' ' + u'\u03bcg' + '/' + u'm\u00b3' + ", " + str(int(100 * df.hpm25_female_popweighted.values[-1] / df_baseline.hpm25_female_popweighted.values[-1])) + "%)"),
('Household ' + u'PM\u2082.\u2085' + ' concentration, males', '(India = ' + str(df.hpm25_male_popweighted.values[-1]) + ' ' + u'\u03bcg' + '/' + u'm\u00b3' + ", " + str(int(100 * df.hpm25_male_popweighted.values[-1] / df_baseline.hpm25_male_popweighted.values[-1])) + "%)"),
('Household ' + u'PM\u2082.\u2085' + ' concentration, children', '(India = ' + str(df.hpm25_child_popweighted.values[-1]) + ' ' + u'\u03bcg' + '/' + u'm\u00b3' + ", " + str(int(100 * df.hpm25_child_popweighted.values[-1] / df_baseline.hpm25_child_popweighted.values[-1])) + "%)"),
('MORT from total ' + u'PM\u2082.\u2085' + ' exposure', '(India = ' + '{:,}'.format(int(round(df.mort_tpm25_6cod_mean_total.values[-1], -3))) + " deaths, " + str(int(100 * df.mort_tpm25_6cod_mean_total.values[-1] / df_baseline.mort_tpm25_6cod_mean_total.values[-1])) + "%)"),
('MORT from ambient ' + u'PM\u2082.\u2085' + ' exposure', '(India = ' + '{:,}'.format(int(round(df.mort_apm25_6cod_mean_total.values[-1], -3))) + " deaths, " + str(int(100 * df.mort_apm25_6cod_mean_total.values[-1] / df_baseline.mort_apm25_6cod_mean_total.values[-1])) + "%)"),
('MORT from household ' + u'PM\u2082.\u2085' + ' exposure', '(India = ' + '{:,}'.format(int(round(df.mort_hpm25_6cod_mean_total.values[-1], -3))) + " deaths, " + str(int(100 * df.mort_hpm25_6cod_mean_total.values[-1] / df_baseline.mort_hpm25_6cod_mean_total.values[-1])) + "%)"),
('DALYs rate from total ' + u'PM\u2082.\u2085' + ' exposure', '(India = ' + '{:,}'.format(int(round(df.dalys_tpm25_rate_6cod_mean_total.values[-1]))) + " DALYs per 100,000 people, " + str(int(100 * df.dalys_tpm25_rate_6cod_mean_total.values[-1] / df_baseline.dalys_tpm25_rate_6cod_mean_total.values[-1])) + "%)"),
('DALYs rate from ambient ' + u'PM\u2082.\u2085' + ' exposure', '(India = ' + '{:,}'.format(int(round(df.dalys_apm25_rate_6cod_mean_total.values[-1]))) + " DALYs per 100,000 people, " + str(int(100 * df.dalys_apm25_rate_6cod_mean_total.values[-1] / df_baseline.dalys_apm25_rate_6cod_mean_total.values[-1])) + "%)"),
('DALYs rate from household ' + u'PM\u2082.\u2085' + ' exposure', '(India = ' + '{:,}'.format(int(round(df.dalys_hpm25_rate_6cod_mean_total.values[-1]))) + " DALYs per 100,000 people, " + str(int(100 * df.dalys_hpm25_rate_6cod_mean_total.values[-1] / df_baseline.dalys_hpm25_rate_6cod_mean_total.values[-1])) + "%)")
]
format_data_abs = []
format_data_diff = []
for plot_index in range(10):
format_data_abs.append(
(
plot_labels[plot_index],
plot_variables[plot_index],
min_values_abs[plot_index],
max_values_abs[plot_index],
'0,0',
plot_title[plot_index][0],
plot_title[plot_index][1]
)
)
format_data_diff.append(
(
plot_labels[plot_index],
plot_variables[plot_index],
min_values_diff[plot_index],
max_values_diff[plot_index],
'0,0',
plot_title[plot_index][0],
plot_title[plot_index][1]
)
)
if scenario == 'BASELINE':
df_format = pd.DataFrame(format_data_abs, columns=['variable_map', 'variable' , 'min_range', 'max_range' , 'format', 'verbage', 'verbage_value'])
else:
df_format = pd.DataFrame(format_data_diff, columns=['variable_map', 'variable' , 'min_range', 'max_range' , 'format', 'verbage', 'verbage_value'])
df_format['scenario'] = scenario
df_formats.update({scenario: df_format})
# --- functions ---
def create_plot(scenario, variable):
geosource = geosources[scenario]
df_format = df_formats[scenario].loc[df_formats[scenario].variable == variable]
if scenario == 'BASELINE':
palette = Viridis10
color_mapper = LinearColorMapper(
palette=palette,
low=df_format['min_range'].values[0],
high=df_format['max_range'].values[0])
else:
palette = BrBG10
color_mapper = LinearColorMapper(
palette=palette,
low=df_format['min_range'].values[0],
high=df_format['max_range'].values[0])
if 'popweighted' in variable:
units = '\u03BCg/m\u00b3'
elif 'mort' in variable:
units = 'deaths'
elif 'dalys' in variable:
units = 'DALYs per 100,000'
plot = figure(
plot_height=400,
plot_width=400,
title=f'{df_format.verbage.values[0]} {df_format.verbage_value.values[0]}',
tools="pan,wheel_zoom,reset,hover,save",
x_axis_location=None,
y_axis_location=None,
tooltips=[
("Scenario", scenario),
("State", "@NAME_1"),
(df_format.verbage.values[0], f"@{variable} {units}"),
("(Lon, Lat)", "($x, $y)")])
plot.grid.grid_line_color = None
plot.hover.point_policy = "follow_mouse"
plot.patches(
'xs', 'ys', source=geosource,
fill_color={'field': variable, 'transform': color_mapper},
fill_alpha=0.7, line_color="grey", line_width=0.5)
color_bar = ColorBar(
color_mapper=color_mapper, ticker=BasicTicker(),
label_standoff=12, border_line_color=None, location=(0,0))
#plot.add_layout(color_bar, 'right')
plot.toolbar.logo = None
plot.toolbar_location = None
return plot
# -- create interactive plot ---
tabs_apm25 = []
variable = 'apm25_popweighted'
for scenario in ['BASELINE', 'ALLLPG']:
plot = create_plot(scenario, variable)
tab = Panel(child=plot, title=scenario)
tabs_apm25.append(tab)
tabs_mort = []
variable = 'mort_tpm25_6cod_mean_total'
for scenario in ['BASELINE', 'ALLLPG']:
plot = create_plot(scenario, variable)
tab = Panel(child=plot, title=scenario)
tabs_mort.append(tab)
show(row(Tabs(tabs=tabs_apm25), Tabs(tabs=tabs_mort)))